package primitive3D;

import static helper.Util.*;

import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

import rayTracer.AABB;
import rayTracer.Ray;
import rayTracer.Shader;
import rayTracer.Transformation;


public class Triangle extends Primitive3D {
	private Point3d a, b, c;	

	// El orden en que se recorren los puntos del triangulo es:
	// Inicia en a, sigue por b y luego por c, en sentido contra reloj.
	public Triangle( Point3d a, Point3d b, Point3d c ){
		this.a = new Point3d(a);
		this.b = new Point3d(b);		
		this.c = new Point3d(c);
		name = new String("");
		components = null;		
		//Set position as the intersection of Medians
		position = new Point3d(this.a);
		position.add(this.b);
		position.add(this.c);
		position.scale(1.0/3.0);
		
		
		Point3d[] pts = new Point3d[]{a, b, c};
		
		double minX = Double.POSITIVE_INFINITY;
		double minY = Double.POSITIVE_INFINITY;
		double minZ = Double.POSITIVE_INFINITY;
		
		double maxX = Double.NEGATIVE_INFINITY;
		double maxY = Double.NEGATIVE_INFINITY;
		double maxZ = Double.NEGATIVE_INFINITY;
		
		for( int i=0 ; i<3 ; i++ ){
			if( pts[i].x < minX )
				minX = pts[i].x;
			if( pts[i].x > maxX )
				maxX = pts[i].x;
			if( pts[i].y < minY )
				minY = pts[i].y;
			if( pts[i].y > maxY )
				maxY = pts[i].y;
			if( pts[i].z < minZ )
				minZ = pts[i].z;
			if( pts[i].z > maxZ )
				maxZ = pts[i].z;
		}
		double[] dmin = new double[]{minX, minY, minZ};
		double[] dmax = new double[]{maxX, maxY, maxZ};
		
		bb = new AABB(new Point3d(dmin), new Point3d(dmax) );
		
		//bb = new AABB(getPoints());
	}
	
	public Triangle( Point3d[]points, Shader shader ){
		this(points[0], points[1], points[2]);
		this.shader = shader;
	}
	
	public void setPosition(Point3d otherPosition) {
		//First move it to the origin
		a.sub(position);
		b.sub(position);
		c.sub(position);
		bb.min.sub(position);
		bb.max.sub(position);
		
		//Now move it to the new position
		a.add(otherPosition);
		b.add(otherPosition);
		c.add(otherPosition);
		bb.min.add(otherPosition);
		bb.max.add(otherPosition);
		position = new Point3d(otherPosition);
	}	
	
	@Override
	public Object clone(){
		Triangle obj=null;
        try{
            obj=(Triangle)super.clone();
        }catch(CloneNotSupportedException ex){
            System.out.println("No se puede duplicar");
        }        
        obj.a = (Point3d)a.clone();
        obj.b = (Point3d)b.clone();
        obj.c = (Point3d)c.clone();        
        return obj;	    
	}
	
	public String toString(){
		StringBuffer str = new StringBuffer();
		
		//str.append("Triangle \"" + getName() + "\"\nA = " + a + "\tB = " + b + "\tC = " + c + "\nPosition = " + position + "\nColor = " + color);
		//str.append("Triangle \"" + getName() + "\""+"  bb.min="+bb.min+"bb.max="+bb.max);
		str.append("Triangle \"" + getName() + "\"\nA = " + a + "\tB = " + b + "\tC = " + c +"  bb.min="+bb.min+"bb.max="+bb.max);
		return str.toString();
	}
	
	@Override
	public void scaleFromReferencePosition(Point3d referencePosition, double scaleFactor) {
		this.scalePointfromReferencePosition(a, referencePosition, scaleFactor);
		this.scalePointfromReferencePosition(b, referencePosition, scaleFactor);
		this.scalePointfromReferencePosition(c, referencePosition, scaleFactor);
		this.scalePointfromReferencePosition(bb.min, referencePosition, scaleFactor);
		this.scalePointfromReferencePosition(bb.max, referencePosition, scaleFactor);
	}
	
	public Point3d[] getPoints() {
		return new Point3d[]{a, b, c};
	}

	public Vector3d getFirstVector() {
		// Devuelve el vector a: a-c
		// c se toma como origen de coordenadas
		return getVector(c, a);
	}
	
	public Vector3d getSecondVector() {
		// Devuelve el vector b: b-c
		return getVector(c, b);
	}
	
	public Vector3d getVecNormal() {
		return helper.Util.getVecNormal(getVector(c, a), getVector(c, b));
	}
	
	// Devuelve un vector normal al triangulo que pasa por p
	// p debe ser un punto de la superficie del triangulo
	public Vector3d getNormal(Point3d p) {
		return helper.Util.getVecNormal(getVector(c, a), getVector(c, b));
	}
	
	public double area() {
		Vector3d vecNormal = getVecNormal();
		return vecNormal.length()/2;
	}
	
	// Devuelve la distancia al punto intersectado en el nombre de
	// la funcion. Si no hay interseccion devuelve -1. 
	public double intersects(Ray ray) {
			Vector3d e1, e2, q, s, r;
			double k, u, v, det;
			
			e1 = getVector(c, a);
			e2 = getVector(c, b);
			q = helper.Util.getVecNormal(ray.vecDir, e2);
			det = e1.dot(q);
			// Si el determinante da 0 es porque el rayo es paralelo al plano del
			// triangulo; si da < 0 el rayo apunta en sentido opuesto al triangulo. 
			if( det <= 0 ) {
				return -1;
			}
			
			// det > 0
			// retraso la division por det ya que u, v solo se necesitan
			// para checks, no se necesita calcularlas con precision
			s = getVector(c, ray.origin);
			u = s.dot(q);
			// Se debe cumplir que: u >=0, v >= 0, u + v <= 1
			// Sino, NO hay interseccion.
			if (u < 0.0 || u > det) {
				return -1;
			}
			
			r = helper.Util.getVecNormal(s, e1);
			v = r.dot(ray.vecDir);
			// Se debe cumplir que: u >=0, v >= 0, u + v <= 1
			// Sino, NO hay interseccion.
			if (v < 0.0 || u + v > det) {
				return -1;
			}
			
			// Para que el rayo corte al triangulo debe ser k>=0
			k = r.dot(e2);
			if( k < 0 ) {
				return -1;
			}
			
			// Finalmente calculo la distancia al punto de interseccion usando:
			k /= det;
			return k;
	}

	@Override
	public int transform(Transformation trans) {
		trans.translateRotate(position);			
		trans.transformPoint(a);
		trans.transformPoint(b);
		trans.transformPoint(c);		
		trans.transformPoint(bb.min);
		trans.transformPoint(bb.max);
		//System.out.println("DESPUESSS DE LA TRASFORMACION");
		//System.out.println("A="+a+"B="+b+"C="+c);
		return 0;
	}
			
}
